/*
* ============================================================================
*  Name        : platform_posix.h
*  Part of     : Finite state machine utilities
*  Description : Platform abstruction layer
*  Version     : %version: %
*  
*
* ============================================================================
*/
/**
 * @file sea_platform_posix.h
 *
 * @ingroup Platforms
 */

#if !defined( __sea_platform_posix__ )
#define __sea_platform_posix__

#define _POSIX_SPIN_LOCKS

#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <typeinfo>
#include <algorithm>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <cstdarg>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
#include "sea_assert.h"

int pthread_spin_destroy( pthread_spinlock_t *lock );
int pthread_spin_init( pthread_spinlock_t *lock, int pshared ); 
int pthread_spin_lock( pthread_spinlock_t *lock );
int pthread_spin_unlock( pthread_spinlock_t *lock );
int pthread_spin_trylock( pthread_spinlock_t *lock );


#if defined( __GNUC__ )
    #if defined( MAEMO )
        #define USE_GNU_ATOMIC
    #endif
#endif

namespace sea
{
    typedef std::string String;
    typedef pthread_t OsThreadHandle;
    typedef pthread_t OsThreadId;

    #define SEA_PRETTY_FUNCTION __PRETTY_FUNCTION__
    #define strtok_s strtok_r
    #define strncpy_s( dest, dlen, src, slen ) strncpy( dest, src, slen )

    /**
     * @class OsThread
     * @brief Thread in posix
     *
     * @ingroup Platforms
     */
    class OsThread : public Object
    {
    public:
        OsThread( ) : m_thread( 0 ), m_started( false ) { }

        ~OsThread( ) { }

        virtual void exec( ){ }

        static void* entry( void* param )
        {
            OsThread* the_thread = reinterpret_cast< OsThread* >( param );
            sea::ref< OsThread > thread = the_thread;
            pthread_attr_init( &the_thread->m_attr );
            
            // Enforse SCHED_FIFO policy
            pthread_attr_setschedpolicy( &the_thread->m_attr, SCHED_FIFO );
            the_thread->exec( );
            the_thread->m_started = false;
            the_thread->m_thread = 0;

            pthread_exit( 0 );
            return 0;
        }
        
        static OsThreadId currentThreadId( ){ return pthread_self( ); }
        void start( );
        void wait( i::uint32 time = -1 );

        OsThreadHandle id( ){ return m_thread; }

        void setPriority( OsThreadPriority value )
        {
            if ( value >= Lowest && value <= Highest )
            {
                int mn = sched_get_priority_min( SCHED_FIFO );
                int mx = sched_get_priority_min( SCHED_FIFO );

                // Deduce priority index to the abs value
                pthread_setschedprio( m_thread, ( ( mx - mn ) / ( Highest - Lowest ) ) * value );
            }
        }
        
        bool isRunning( ){ return m_thread != 0 && m_started; }

    private:
        pthread_t m_thread;
        bool m_started;

        pthread_attr_t m_attr;
        struct sched_param m_sched_param;
    };

    /**
     * @class OsSemaphore
     * @brief Semaphore in posix
     *
     * @ingroup Platforms
     */
    class OsSemaphore : public Object
    {
    public:
        OsSemaphore( int init_counter = 0 )
        { 
            if( sem_init( &m_semaphore, 0, init_counter ) != 0 )
            {
                ASSERT_DEBUG( false );
            }
        }
        ~OsSemaphore( ) { sem_destroy( &m_semaphore ); }
        
        virtual void signal( int number = 1 ) 
        { 
            while( number-- )
            {
                sem_post( &m_semaphore );
            }
        }

        virtual bool wait( int number = 1, int timeout = -1 )
        { 
            if( timeout != -1 && number > 1 )
            {
                number = 1;
            }

            while( number-- )
            {
                int ret;
                if ( timeout == -1 )
                {
                    ret = sem_wait( &m_semaphore );
                }
                else
                {
                    const struct timespec timeout = 
                        { timeout / 1000, ( timeout % 1000 ) * 1000000 };
                    ret = sem_timedwait( &m_semaphore, &timeout );
                }
                if ( ret != 0 )
                {
                    return false;
                }
            }
            return true;
        }
    private:
        sem_t m_semaphore;
    };

    /**
     * @class OsMutex
     * @brief Mutex in posix
     *
     * @ingroup Platforms
     */
    class OsMutex : public Object
    {
    public:
        OsMutex( )
        {
            pthread_mutexattr_t attr;
            if ( pthread_mutexattr_init( &attr ) != 0 )
            {
                ASSERT_DEBUG( false );
            }

            if ( pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ) != 0 )
            {
                ASSERT_DEBUG( false );
            }  

            if ( pthread_mutex_init( &m_mutex, &attr ) != 0 )
            {
                ASSERT_DEBUG( false );
            }
        }

        ~OsMutex( )
        {
            if ( pthread_mutex_destroy( &m_mutex ) != 0 )
            {
                ASSERT_DEBUG( false );
            }
        }

        bool try_enter( ) { return pthread_mutex_trylock( &m_mutex ) != EBUSY; }

        void enter( )
        { 
            if ( pthread_mutex_lock( &m_mutex ) != 0 )
            {
                ASSERT_DEBUG( false );
            }
        }

        void leave( )
        { 
            if ( pthread_mutex_unlock( &m_mutex ) != 0 )
            {
                ASSERT_DEBUG( false );
            }
        }

        /**
         * @class Scoped
         * @brief Mutex scoped lock/unlock
         *
         * @ingroup Platforms
         */
        class Scoped
        {
        public:
            Scoped( OsMutex* critical_section, bool do_try = false );
            ~Scoped( );
        private:
            OsMutex* m_mutex;
        };

    private:
        pthread_mutex_t m_mutex;
    };

    /**
     * @class OsSpinlock
     * @brief Spinlock in Linux
     *
     * @ingroup Platforms
     */
    class OsSpinlock
    {
    public:
        OsSpinlock( ) 
        {
            if ( pthread_spin_init( &m_value, 0 ) )
            {
                ASSERT_DEBUG( false );
            }
        }
        ~OsSpinlock( )
        {
            if( pthread_spin_destroy( &m_value ) )
            {
                ASSERT_DEBUG( false );
            }
        }

        void enter( )
        {
            if( pthread_spin_lock( &m_value ) )
            {
                ASSERT_DEBUG( false );
            }
        }

        void leave( )
        {
            if( pthread_spin_unlock( &m_value ) )
            {
                ASSERT_DEBUG( false );
            }
        }

        /**
         * @class Scoped
         * @brief Mutex scoped lock/unlock
         *
         * @ingroup Platforms
         */
        class Scoped
        {
        public:
            Scoped( OsSpinlock* critical_section ): m_lock( critical_section ) { if ( m_lock ) m_lock->enter( ); }
            ~Scoped( ){ if ( m_lock ) m_lock->leave( ); }
        private:
            OsSpinlock* m_lock;
        };
    private:
        pthread_spinlock_t m_value;
    };



    /**
     * @class OsSignal
     * @brief Signal in posix
     *
     * @ingroup Platforms
     */
    class OsSignal : public OsSemaphore
    {
    public:
        OsSignal( ) : OsSemaphore( 0 ) { }
        bool wait( int timeout )
        {
            OsMutex::Scoped protect( &m_mutex );

            // True - wait period is finished, False - otherwise
            return !OsSemaphore::wait( 1, timeout ); 
        }
        void wakeup( ){ OsSemaphore::signal( ); }
    private:
        OsMutex m_mutex;
    };


    /**
     * @class OsTime
     * @brief Millisecond timer
     *
     * @ingroup Platforms
     */
    class OsTime
    {
    public:
        OsTime( ) : m_current( get_time_in_ms( ) ) { }

        /**
         * Start counting time
         */
        void start( ){ m_current = get_time_in_ms( ); }

        /**
         * Get elapsed time
         *
         * @return Time in milliseconds
         */
        int elapsed( ) { return get_time_in_ms( ) - m_current; }

        /**
         * Get time in milliseconds
         */
        static i::uint32 get_time_in_ms( )
        {
            static i::int64 seed = 0;
            struct timeval tv;
            struct timezone tz;
            i::int64 tmp;

            gettimeofday( &tv, &tz );
            tmp = ( i::int64 )tv.tv_sec * 1000000 + ( i::int64 )tv.tv_usec;

            if( seed == 0 )
            {
                seed = tmp;
                return 0;
            }
            return ( tmp - seed ) / 1000;
        }

    private:
        i::uint32 m_current;
    };

    /**
     * File open prototype
     * @brief substitude in Linux
     */
    int fopen_s( FILE** file, const char *file_name, const char *mode );
}

#endif

